package com.bobo.web.provider.util.wxUtils;

/**
 * @author xuehui7
 * @date 2018/10/20
 * @desc
 */

import com.bobo.web.provider.util.RedisUtils;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.bobo.web.api.constants.Constant;
import com.bobo.web.api.domain.order.BusinessPrePayParamsDO;
import com.bobo.web.api.domain.order.JsAPIConfig;
import com.bobo.web.api.vo.user.WechatAccessTokenCheck;
import com.bobo.web.api.vo.user.WechatUserinfo;
import com.bobo.web.provider.util.commutils.HttpClientUtils;
import com.bobo.web.provider.util.commutils.JacksonUtils;
import com.bobo.web.provider.util.commutils.MD5Util;
import net.sf.json.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.net.ssl.*;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.ConnectException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class WeChatUtils {
    @Resource
    private JacksonUtils jacksonUtils;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private MyConfig myConfig;
    @Value("${basketball.isTest}")
    private Boolean isTest;
    /**
     * 获取openID，code等等appSecret
     */
    @Value("${basketball.wechat.secret}")
    private String secret;
    private static Logger LOGGER = LoggerFactory.getLogger(WeChatUtils.class.getSimpleName());
    // 获取网页授权access_token的Url，和基础服务access_token不同，记得区分
    private static String getAccessTokenUrl =
            "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
    // 获取access_token的Url，基础服务access_token
    private static String BASE_ACCESS_TOKEN_URL =
            "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    // 刷新网页授权access_token的Url，和基础服务access_token不同，记得区分
    private static String getRefreshAccessTokenUrl =
            "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
    // 检验授权凭证access_token是否有效,和基础服务access_token不同，记得区分
    private static String checkAccessTokenUrl = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID";
    // 获取用户信息的Url
    private static String getWXUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
    // 获取用户信息的Url
    private static String get_jsapi_ticket = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";

    /**
     * 根据code获取到网页授权access_token
     *
     * @param code 微信回调后带有的参数code值
     * @author xuehui7
     */
    public static WeChatAccessToken getAccessToken(String code, String appId, String secret) {
        String url = getAccessTokenUrl.replace("APPID", appId).replace("SECRET", secret).replace("CODE", code);
        JSONObject jsonObj = JSONObject.fromObject(httpRequest(url, "POST", null));
        return (WeChatAccessToken) JSONObject.toBean(jsonObj, WeChatAccessToken.class);
    }

    /**
     * 获取
     *
     * @return
     */
    public String getAccessToken() {
        String accessToken = null;
        if (redisUtils.getValueBykeys(Constant.ACCESS_TOKEN) != null) {
            accessToken = (String) redisUtils.getValueBykeys(Constant.ACCESS_TOKEN);
        }
        if (StringUtils.isBlank(accessToken)) {
            LOGGER.info("accessToken已过期,重新获取");
            String requestUrl = BASE_ACCESS_TOKEN_URL.replace("APPID", myConfig.getAppID()).replace("APPSECRET", secret);
            JSONObject jsonObject = null;
            try {
                JSONObject jsonObj = JSONObject.fromObject(httpRequest(requestUrl, "GET", null));
                Object access_token_Object = jsonObj.get("access_token");
                LOGGER.info("请求accessToken返回：" + jacksonUtils.toJson(jsonObj));
                if (access_token_Object != null) {
                    accessToken = (String) access_token_Object;
                    if (StringUtils.isNotBlank(accessToken)) {
                        LOGGER.info("获取到到accessToken值=" + accessToken);
                        redisUtils.add(Constant.ACCESS_TOKEN, accessToken, 7100, TimeUnit.SECONDS);
                    }
                }
            } catch (Exception e) {
                LOGGER.error("获取accessToken异常", e);
            }
        } else {
            LOGGER.info("从缓存中获取获取accessToken成功");
        }
        return accessToken;
    }

    /**
     * 根据在获取accessToken时返回的refreshToken刷新accessToken
     *
     * @param refreshToken
     * @author xuehui7
     */
    public static WeChatAccessToken getRefreshAccessToken(String refreshToken, String appId) {
        String url = getRefreshAccessTokenUrl.replace("APPID", appId).replace("REFRESH_TOKEN", refreshToken);
        JSONObject jsonObj = JSONObject.fromObject(httpRequest(url, "POST", null));
        return (WeChatAccessToken) JSONObject.toBean(jsonObj, WeChatAccessToken.class);
    }

    /**
     * 获取微信用户信息
     *
     * @param openId      微信标识openId
     * @param accessToken 微信网页授权accessToken
     * @author xuehui7
     */
    public static WechatUserinfo getWXUserInfoUrl(String openId, String accessToken) {
        String url = getWXUserInfoUrl.replace("OPENID", openId).replace("ACCESS_TOKEN", accessToken);
        JSONObject jsonObj = JSONObject.fromObject(httpRequest(url, "POST", null));
        return (WechatUserinfo) JSONObject.toBean(jsonObj, WechatUserinfo.class);
    }

    /**
     * 检验授权凭证accessToken是否有效
     *
     * @param openId
     * @param accessToken
     * @author xuehui7
     */
    public WechatAccessTokenCheck checkAccessToken(String openId, String accessToken) {
        String url = checkAccessTokenUrl.replace("OPENID", openId).replace("ACCESS_TOKEN", accessToken);
        JSONObject jsonObj = JSONObject.fromObject(httpRequest(url, "POST", null));
        return (WechatAccessTokenCheck) JSONObject.toBean(jsonObj, WechatAccessTokenCheck.class);
    }

    /**
     * 检验授权凭证accessToken是否有效
     *
     * @param accessToken
     * @author xuehui7
     */
    public static String getJsapiTicket(String accessToken) {
        String url = get_jsapi_ticket.replace("ACCESS_TOKEN", accessToken);
        JSONObject jsonObj = JSONObject.fromObject(httpRequest(url, "GET", null));
        String ticket = (String) jsonObj.get("ticket");
        return ticket;
    }

    /**
     * get或者post请求
     *
     * @param requestUrl
     * @param requestMethod GET or POST 需要大写*
     * @param outputStr
     * @return
     * @author xuehui7
     */
    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        StringBuffer buffer = new StringBuffer();
        try {
            // 创建SSLContext对象，并使用我们指定的信任管理器初始化
            X509TrustManager x509m = getX509TrustManager();
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, new TrustManager[]{x509m}, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL url = new URL(requestUrl);
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);
            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            // 设置请求方式（GET/POST）
            httpUrlConn.setRequestMethod(requestMethod);
            if ("GET".equalsIgnoreCase(requestMethod))
                httpUrlConn.connect();
            // 当有数据需要提交时
            if (null != outputStr) {
                OutputStream outputStream = httpUrlConn.getOutputStream();
                // 注意编码格式，防止中文乱码
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 将返回的输入流转换成字符串
            InputStream inputStream = httpUrlConn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            // 释放资源
            inputStream.close();
            inputStream = null;
            httpUrlConn.disconnect();
        } catch (ConnectException ce) {
            LOGGER.error("Weixin server connection timed out." + ce.getMessage());
        } catch (Exception e) {
            LOGGER.error("https request error:{}" + e.getMessage());
        }
        return buffer.toString();
    }

    private static X509TrustManager getX509TrustManager() {
        return new X509TrustManager() {

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException {
            }
        };
    }

    public JsAPIConfig unifiedOrder(BusinessPrePayParamsDO businessPrePayParamsDO) {
        LOGGER.info(String.format("获取微信预支付ID的参数：%s", jacksonUtils.toJson(businessPrePayParamsDO)));
        WXPay wxpay = null;
        try {
            if (isTest) {
                wxpay = new WXPay(myConfig, WXPayConstants.SignType.MD5, true);
            } else {
                wxpay = new WXPay(myConfig);
            }
        } catch (Exception e) {
            LOGGER.error("初始化微信配置异常", e);
            return null;
        }
        Map<String, String> data = new HashMap<String, String>();
        data.put("body", businessPrePayParamsDO.getBody());
        data.put("out_trade_no", businessPrePayParamsDO.getPlatOrderId());
        data.put("fee_type", "CNY");
        data.put("total_fee", businessPrePayParamsDO.getTotalAmount() + "");
        data.put("spbill_create_ip", "123.12.12.123");
        data.put("notify_url", businessPrePayParamsDO.getNotifyUrl());
        data.put("trade_type", "JSAPI");  // 此处指定为扫码支付
        data.put("openid", "oPylVxCIyRJavUipBmKfNfpSsKII");
        data.put("openid", businessPrePayParamsDO.getOpenId());
        try {
            Map<String, String> resp = wxpay.unifiedOrder(data);
            LOGGER.info("获取预支付ID返回值"+jacksonUtils.toJson(resp));
            return createPayConfig(resp.get("prepay_id"));
        } catch (Exception e) {
            LOGGER.error("微信统一下单支付异常", e);
        }

        return null;
    }

    public JsAPIConfig createPayConfig(String prepayId) {
        JsAPIConfig config = new JsAPIConfig();
        String nonce = UUID.randomUUID().toString();
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        String packageName = "prepay_id=" + prepayId;
        StringBuffer sign = new StringBuffer();
        sign.append("appId=").append(myConfig.getAppID());
        sign.append("&nonceStr=").append(nonce);
        sign.append("&package=").append(packageName);
        sign.append("&signType=").append(config.getSignType());
        sign.append("&timeStamp=").append(timestamp);
        sign.append("&key=").append(myConfig.getKey());
        String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase();

        config.setAppId(myConfig.getAppID());
        config.setNonce(nonce);
        config.setTimestamp(timestamp);
        config.setPackageName(packageName);
        config.setSign(signature);

        return config;
    }

    public static String computeWeChatPaySign(SortedMap<String, String> treeMap, String weChatKey) {
        StringBuffer stringBufferSignTemp = new StringBuffer();
        treeMap.forEach((String key, String value) -> {
            stringBufferSignTemp.append(key + "=" + value + "&");
        });

        String stringSignTemp = stringBufferSignTemp.substring(0, stringBufferSignTemp.length() - 1)
                + "&key=" + weChatKey;
        System.out.println(stringSignTemp);
        return MD5Util.MD5Encode(stringSignTemp, "UTF-8").toUpperCase();
    }

    /**
     * Bean --> Map 1: 利用Introspector和PropertyDescriptor 将Bean --> Map
     *
     * @param obj
     * @return
     */
    public static void transBean2SignMap(Object obj, Map<String, String> map) {

        if (obj == null) {
            return;
        }
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();

                // 过滤class属性
                if (!key.equals("class")) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);
                    if (null != value) {
                        map.put(key, String.valueOf(value));
                    }
                }

            }
        } catch (Exception e) {
            System.out.println("transBean2Map Error " + e);
        }
    }

    /**
     * xml转map 不带属性
     *
     * @param needRootKey 是否需要在返回的map里加根节点键
     */
    public static TreeMap xml2map(String xmlStr, boolean needRootKey) throws DocumentException {
        Document doc = DocumentHelper.parseText(xmlStr);
        Element root = doc.getRootElement();
        TreeMap<String, Object> map = xml2map(root);
        if (root.elements().size() == 0 && root.attributes().size() == 0) {
            return map;
        }
        if (needRootKey) {
            //在返回的map里加根节点键（如果需要）
            TreeMap<String, Object> rootMap = new TreeMap<>();
            rootMap.put(root.getName(), map);
            return rootMap;
        }
        return map;
    }

    /**
     * xml转map 不带属性
     */
    private static TreeMap xml2map(Element e) {
        TreeMap map = new TreeMap();
        List list = e.elements();
        if (list.size() > 0) {
            for (int i = 0; i < list.size(); i++) {
                Element iter = (Element) list.get(i);
                List mapList = new ArrayList();

                if (iter.elements().size() > 0) {
                    Map m = xml2map(iter);
                    if (map.get(iter.getName()) != null) {
                        Object obj = map.get(iter.getName());
                        if (!(obj instanceof List)) {
                            mapList = new ArrayList();
                            mapList.add(obj);
                            mapList.add(m);
                        }
                        if (obj instanceof List) {
                            mapList = (List) obj;
                            mapList.add(m);
                        }
                        map.put(iter.getName(), mapList);
                    } else {
                        map.put(iter.getName(), m);
                    }
                } else {
                    if (map.get(iter.getName()) != null) {
                        Object obj = map.get(iter.getName());
                        if (!(obj instanceof List)) {
                            mapList = new ArrayList();
                            mapList.add(obj);
                            mapList.add(iter.getText());
                        }
                        if (obj instanceof List) {
                            mapList = (List) obj;
                            mapList.add(iter.getText());
                        }
                        map.put(iter.getName(), mapList);
                    } else {
                        map.put(iter.getName(), iter.getText());
                    }
                }
            }
        } else {
            map.put(e.getName(), e.getText());
        }
        return map;
    }

    /**
     * 获取微信沙箱环境签名key
     *
     * @return
     */
    public static String getSandboxSignkey() {
        String url = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey";
        Map<String, String> params = new HashMap<>();
        String nonce = UUID.randomUUID().toString().replace("-","");
        params.put("mch_id", "1491819762");//微信商户号
        params.put("nonce_str", nonce);
        try {
            String sign = WXPayUtil.generateSignature(params, "840e2dadabe54046b207d0154ec1c19e", WXPayConstants.SignType.MD5);
            params.put("sign", sign);
        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        try {
            result = HttpClientUtils.creatPostAndTransData(url, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(result);
        return result;
    }


    public static void main(String[] args) throws Exception {

        MyConfig config = new MyConfig();
        WXPay wxpay = new WXPay(config);

        Map<String, String> data = new HashMap<String, String>();
        data.put("body", "腾讯充值中心-QQ会员充值");
        data.put("out_trade_no", "2016090910595900000012");
        data.put("device_info", "");
        data.put("fee_type", "CNY");
        data.put("total_fee", "1");
        data.put("spbill_create_ip", "123.12.12.123");
        data.put("notify_url", "http://www.example.com/wxpay/notify");
        data.put("trade_type", "JSAPI");  // 此处指定为扫码支付
        data.put("product_id", "12");
        data.put("openid", "oIIyk0ugYkBiZ44J-FpgXFjKZoS8");

        try {
            Map<String, String> resp = wxpay.unifiedOrder(data);
            System.out.println(resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //System.out.println(UUID.randomUUID().toString().replace("-",""));
    }

}

